using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Database_Interface.CIM;
using System.Drawing;
namespace MacomberMap.Loader.Export.OneLines
{
    /// <summary>
    /// This class is responsible for validating a one-line's topology
    /// </summary>
    public static class MM_OneLine_Validation
    {
        /// <summary>
        /// Run through the topology, to locate any errors
        /// </summary>
        /// <param name="xOneLine">The one-line in question</param>
        /// <param name="BaseElement">The CIM base element</param>
        /// <param name="CIM">The CIM database to be used</param>
        /// <param name="Errors">The number of found errors</param>
        /// <param name="Warnings">The number of found warnings</param>
        public static string ValidateTopology(XmlElement xOneLine, CIM_Element BaseElement, int CIM, out int Errors, out int Warnings)
        {
            List<String> ExcludeTypes = new List<string>(5);
            ExcludeTypes.Add("BusbarSection");
            ExcludeTypes.Add("Bay");
            ExcludeTypes.Add("ConnectivityNodeGroup");
            ExcludeTypes.Add("SynchrocheckRelay");
            ExcludeTypes.Add("FrequencyRelay");
            ExcludeTypes.Add("StationSupply");
            ExcludeTypes.Add("VoltageRelay");
            DateTime StartTime = DateTime.Now;
            Errors = 0;
            Warnings = 0;
            CIM_Element BaseStation = MM_Database_CIM.TEIDs[Convert.ToUInt64(xOneLine.Attributes["BaseElement.TEID"].Value)];
            StringBuilder sB = new StringBuilder();
            sB.AppendLine("Topology validation ran on " + DateTime.Now.ToString());
            sB.AppendLine("Base element: " + BaseElement.Type.Split(':')[1] + " " + BaseElement.Name);

            //First, build our dictionary of components in the XML
            UInt64 ElemTEID;
            Dictionary<UInt64, XmlElement> InOneLine = new Dictionary<ulong, XmlElement>();
            foreach (XmlElement xElemCategory in xOneLine.ChildNodes)
                foreach (XmlElement xElem in xElemCategory.ChildNodes)
                    if (xElem.HasAttribute("BaseElement.TEID"))
                        if (InOneLine.ContainsKey(ElemTEID = Convert.ToUInt64(xElem.Attributes["BaseElement.TEID"].Value)))
                            sB.AppendLine(string.Format("WRN {0:000}: {1} {2} appears more than once in the one-line!", ++Warnings, xElem.Name, xElem.Attributes["BaseElement.TEID"].Value));
                        else
                            InOneLine.Add(ElemTEID, xElem);

            //Now, add in all pricing vectors
            foreach (XmlElement xPricingVector in xOneLine.SelectNodes("//PricingVector"))
                if (InOneLine.ContainsKey(Convert.ToUInt64(xPricingVector.Attributes["BaseElement.TEID"].Value)))
                    sB.AppendLine(string.Format("WRN {0:000}: {1} {2} appears more than once in the one-line!", ++Warnings, xPricingVector.Name, xPricingVector.Attributes["BaseElement.TEID"].Value));
                else
                    InOneLine.Add(Convert.ToUInt64(xPricingVector.Attributes["BaseElement.TEID"].Value), xPricingVector);


            //Then, build our list of all CIM elements we're interested in
            Dictionary<UInt64, CIM_Element> InStation = new Dictionary<ulong, CIM_Element>();
            CIM_Element Substation = MM_Database_CIM.TEIDs[XmlConvert.ToUInt64(xOneLine.Attributes["BaseElement.TEID"].Value)];
            foreach (CIM_Element SubElem in Substation.Elements)
                if (!ExcludeTypes.Contains(SubElem.Type.Split(':')[1]) && !string.IsNullOrEmpty(SubElem.TEID))
                {
                    CIM_Element OutElem = SubElem.Type == "cim:TransformerWinding" ? SubElem.Parse("cim:PowerTransformer") : SubElem;
                    if (!InStation.ContainsKey(Convert.ToUInt64(OutElem.TEID)))
                        InStation.Add(Convert.ToUInt64(OutElem.TEID), OutElem);
                }

            //First ensure all elements in CIM are in the one-line
            foreach (KeyValuePair<UInt64, CIM_Element> kvp in InStation)
                if (!InOneLine.ContainsKey(kvp.Key))
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is in the CIM representation of the substation, but not on the one-line.", ++Errors, kvp.Value.Type.Split(':')[1], kvp.Value.Name));

            //Now, go through every XML component in the one-line, and ensure it's configured properly based on CIM
            CIM_Element Elem, FoundElem2;
            foreach (KeyValuePair<UInt64, XmlElement> kvp in InOneLine)
                if (!InStation.TryGetValue(kvp.Key, out Elem))
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is in the one-line representation of the substation, but not on the CIM model.", ++Errors, kvp.Value.Name, kvp.Value.Attributes["BaseElement.Name"].Value));
                else
                {
                    //Check to see whether the element has a descriptor if needed
                    if (kvp.Value.Name == "Node" && (FoundElem2 = Elem.Parse("cim:BusbarSection")) != null && kvp.Value["Descriptor"] == null)
                        sB.AppendLine(String.Format("WRN {0:000}: {1} {2} is in the one-line and is associated with busbar section {3}, but does not have a descriptor.", ++Warnings, Elem.Type.Split(':')[1], Elem.Name, FoundElem2.Name));
                    else if (kvp.Value.Name == "PricingVector" && kvp.Value["Descriptor"] == null)
                        sB.AppendLine(String.Format("WRN {0:000}: {1} {2} ({3}) is in the one-line, but does not have a descriptor.", ++Warnings, Elem.Type.Split(':')[1], Elem.Name, Elem.VoltageLevel.Name));

                    //If a switch or breaker, check the normal status
                    if ((kvp.Value.Name == "Switch" || kvp.Value.Name == "Breaker") && XmlConvert.ToBoolean(Elem["cim:Switch.normalOpen"]) != (kvp.Value.Attributes["Opened"].Value == "Checked"))
                        sB.AppendLine(String.Format("ERR {0:000}: The normal open status of {1} {2} does not match between the one-line and CIM model.", ++Errors, Elem.Type.Split(':')[1], Elem.Name));

                    //If a node, ensure it's in place.
                    if (kvp.Value.Name == "Node")
                    {
                        //Make sure if we have a resource node, a secondary descriptor is there and matches.
                        String RN = Elem["etx:ElectricalBus<etx:ResourceNode>name"];
                        if (!String.IsNullOrEmpty(RN) && !kvp.Value.HasAttribute("ResourceNode"))
                            sB.AppendLine(String.Format("ERR {0:000}: The resource name for {1} {2} is not found in the one-line: CIM={3}", ++Errors, Elem.ElemType, Elem.Name, RN));
                        else if ((kvp.Value.HasAttribute("ResourceNode") || !String.IsNullOrEmpty(RN)) && !string.Equals(kvp.Value.Attributes["ResourceNode"].Value, RN, StringComparison.CurrentCultureIgnoreCase))
                            sB.AppendLine(String.Format("ERR {0:000}: The resource name for {1} {2} differs: MM={3}, CIM={4}", ++Errors, Elem.ElemType, Elem.Name, kvp.Value.Attributes["ResourceNode"].Value, RN));
                        if (!String.IsNullOrEmpty(RN) && kvp.Value["SecondaryDescriptor"] == null)
                        {
                            bool FoundRN = false;
                            foreach (XmlElement xCh in kvp.Value.ChildNodes)
                                if (xCh.Name == "PokePoint" && xCh["SecondaryDescriptor"] != null)
                                    FoundRN = true;
                            if (!FoundRN)
                                sB.AppendLine(String.Format("WRN {0:000}: {1} {2} contains resource node {3}, but no secondary descriptor is present.", ++Warnings, Elem.ElemType, Elem.Name, RN));
                        }

                        //Build our list of all the places a node points
                        Dictionary<UInt64, XmlElement> NodePointers = new Dictionary<ulong, XmlElement>();
                        foreach (XmlElement xNodeElem in kvp.Value.ChildNodes)
                        {
                            if (xNodeElem.HasAttribute("TEID"))
                                if (NodePointers.ContainsKey(Convert.ToUInt64(xNodeElem.Attributes["TEID"].Value)))
                                    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} is referenced more than once from a node!", ++Warnings, xNodeElem.Name, xNodeElem.Attributes["TEID"].Value));
                                else
                                    NodePointers.Add(Convert.ToUInt64(xNodeElem.Attributes["TEID"].Value), xNodeElem);
                            foreach (XmlElement xNodeChild in xNodeElem.ChildNodes)
                                if (xNodeChild.Name == "PricingVector")
                                    if (NodePointers.ContainsKey(Convert.ToUInt64(xNodeChild.Attributes["BaseElement.TEID"].Value)))
                                        sB.AppendLine(String.Format("WRN {0:000} : {1} {2} is referenced twice in the one-line.", ++Warnings, Elem.ElemType, Elem.Name));
                                    else
                                        NodePointers.Add(Convert.ToUInt64(xNodeChild.Attributes["BaseElement.TEID"].Value), xNodeChild);
                        }


                        //Go through CIM, and find any node to element links that are missing
                        foreach (CIM_Element NodeElem2 in Elem.Elements)
                        {
                            CIM_Element NodeElem = NodeElem2.Type == "cim:TransformerWinding" ? NodeElem2.Parse("cim:PowerTransformer", null) : NodeElem2;
                            if (!ExcludeTypes.Contains(NodeElem.Type.Split(':')[1]) && !InOneLine.ContainsKey(Convert.ToUInt64(NodeElem.TEID)))
                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should be connected to {3} {4}, but it is not on the display.", ++Errors, Elem.ElemType, Elem.Name, NodeElem.ElemType, NodeElem.Name));
                            else if (!ExcludeTypes.Contains(NodeElem.Type.Split(':')[1]) && !NodePointers.ContainsKey(Convert.ToUInt64(NodeElem.TEID)))
                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should be connected to {3} {4}, but it is not.", ++Errors, Elem.ElemType, Elem.Name, NodeElem.ElemType, NodeElem.Name));
                            else
                                NodePointers.Remove(Convert.ToUInt64(NodeElem.TEID));
                        }

                        //Report any no longer valid links in the one-line
                        foreach (XmlElement xNodeRef in NodePointers.Values)
                            if (xNodeRef.Name == "PricingVector")
                                sB.AppendLine(String.Format("ERR {0:000} : The one-line references {1} {2}, but CIM topology does not.", ++Errors, xNodeRef.Name, xNodeRef.Attributes["BaseElement.Name"].Value));
                            else
                                sB.AppendLine(String.Format("ERR {0:000} : The one-line references {1} {2}, but CIM topology does not.", ++Errors, xNodeRef.Name, xNodeRef.OwnerDocument.DocumentElement.SelectSingleNode("Elements/" + xNodeRef.Name + "[@BaseElement.TEID='" + xNodeRef.Attributes["TEID"].Value + "']").Attributes["BaseElement.Name"].Value));
                    }
                    //Validate our pricing vectors, ensure everthing matches up.
                    else if (kvp.Value.Name == "PricingVector")
                    {
                        //Determine our pricing vector's associated node and element
                        CIM_Element PV = InStation[kvp.Key];
                        CIM_Element PVNode = null, PVElem = null;
                        foreach (CIM_Element TermElem in PV.Parse("cim:Terminal").Links)                        
                            if (TermElem.Type == "cim:ConnectivityNode")
                                PVNode = TermElem;
                            else if (TermElem.Type != "etx:PricingVector")
                                PVElem = TermElem;
                        if (PVElem.Type == "cim:TransformerWinding")
                            PVElem = PVElem.Parse("cim:PowerTransformer", null);

                        if (kvp.Value.ParentNode.Attributes["TEID"].Value != PVElem.TEID)
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should be pointing to {3} {4}, but instead is pointing to {5} {6}.", ++Errors, PV.Type.Split(':')[1], PV.Name, PVElem.Type.Split(':')[1], PVElem.Name, kvp.Value.ParentNode.Name, kvp.Value.ParentNode.Attributes["TEID"].Value));
                        if (kvp.Value.ParentNode.ParentNode.Attributes["BaseElement.TEID"].Value != PVNode.TEID)
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should be pointing to {3} {4}, but instead is pointing to {5} {6}.", ++Errors, PV.Type.Split(':')[1], PV.Name, PVNode.Type.Split(':')[1], PVNode.Name, kvp.Value.ParentNode.ParentNode.Name, kvp.Value.ParentNode.ParentNode.Attributes["BaseElement.Name"].Value));



                        //Make sure they both point in the same direction
                        bool OneLinePositive = XmlConvert.ToBoolean(kvp.Value.Attributes["IsPositive"].Value);
                        bool ModelPositive = XmlConvert.ToBoolean(Elem["etx:PricingVector.positiveFlowIn"]);
                        if (OneLinePositive != ModelPositive)
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2}'s vector has an opposite 'IsPositive' value from CIM.", ++Errors, Elem.ElemType, Elem.Name));

                        //Make sure they're associated to the same EPS meters and have the same RIDs
                        CIM_Element EPSMeter = Elem.Parse("etx:EPSMeter", null);
                        if (kvp.Value.Attributes["EPSMeter.TEID"].Value != EPSMeter.TEID)
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} refers to EPS Meter {3}, not {4}.", ++Errors, Elem.ElemType, Elem.Name, Elem.Parse("etx:EPSMeter", null).ToString(), kvp.Value.Attributes["BaseElement.EPSMeter"].Value));

                        if (!String.Equals(kvp.Value.Attributes["EPSMeter.RID"].Value, EPSMeter["etx:EPSMeter.RID"], StringComparison.CurrentCultureIgnoreCase))
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} refers to EPS Meter RID {3}, not {4}.", ++Errors, Elem.ElemType, Elem.Name, kvp.Value.Attributes["EPSMeter.RID"].Value, EPSMeter["etx:EPSMeter.RID"]));


                        //Make sure the orientation is correct

                        try
                        {
                            double Dist, Angle, DeltaX, DeltaY;
                            bool WithinX, WithinY;
                            //Determine the pricing vector's last and next elements
                            Rectangle LastElem, NextElem;
                            if (kvp.Value.PreviousSibling != null)
                                LastElem = GetRectangle(kvp.Value.PreviousSibling.Attributes["Bounds"].Value, null);
                            else
                                LastElem = GetRectangle(kvp.Value.ParentNode.ParentNode.Attributes["Bounds"].Value, null);

                            if (kvp.Value.NextSibling != null)
                                NextElem = GetRectangle(kvp.Value.NextSibling.Attributes["Bounds"].Value, null);
                            else
                                NextElem = GetRectangle(kvp.Value.OwnerDocument.SelectSingleNode("//" + kvp.Value.ParentNode.Name + "[@BaseElement.TEID='" + kvp.Value.ParentNode.Attributes["TEID"].Value + "']").Attributes["Bounds"].Value, null);



                            ComputeDistanceAndAngle(LastElem, NextElem, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);
                            String Orientation;
                            if (Math.Abs(DeltaX) > Math.Abs(DeltaY))
                                Orientation = (DeltaX > 0 ? "Right" : "Left");
                            else
                                Orientation = (DeltaY > 0 ? "Up" : "Down");


                            if (Orientation != kvp.Value.Attributes["Orientation"].Value)
                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should have its node's direction = {3}, but it's {4}.", ++Errors, Elem.ElemType, Elem.Name, Orientation, kvp.Value.Attributes["Orientation"]));
                        }
                        catch (Exception ex)
                        {
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} node direction couldn't be computed: {3}", ++Errors, Elem.ElemType, Elem.Name, ex.Message));
                        }
                    }
                }


            sB.AppendLine();
            sB.AppendLine(String.Format("Topology validation completed in {0}, with {1:#,##0} errors and {2:#,##0} warnings", DateTime.Now - StartTime, Errors, Warnings));
            return sB.ToString();
        }

        private static Rectangle GetRectangle(string p, object p_2)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Compute the closest distance and angle between two points
        /// </summary>
        /// <param name="BaseRect">Our base rectangle</param>
        /// <param name="OtherRect">The rectangle for comparison</param>
        /// <param name="Dist">The distance between the two rectangles</param>
        /// <param name="Angle">The angle between the two rectangles</param>
        /// <param name="DeltaX">The difference between Xs</param>
        /// <param name="DeltaY">The difference between Ys</param>
        /// <param name="WithinX">Whether the other X is within the base left to right</param>
        /// <param name="WithinY">Whether the other Y is within the base top to bottom</param>
        public static void ComputeDistanceAndAngle(Rectangle BaseRect, Rectangle OtherRect, out double Dist, out double Angle, out double DeltaX, out double DeltaY, out bool WithinX, out bool WithinY)
        {
            DeltaX = ClosestToZero(OtherRect.Left - BaseRect.Left, OtherRect.Left - BaseRect.Right, OtherRect.Right - BaseRect.Left, OtherRect.Right - BaseRect.Right);
            DeltaY = ClosestToZero(BaseRect.Top - OtherRect.Top, BaseRect.Top - OtherRect.Bottom, BaseRect.Bottom - OtherRect.Top, BaseRect.Bottom - OtherRect.Bottom);
            Dist = Math.Sqrt((DeltaX * DeltaX) + (DeltaY * DeltaY));
            WithinX = (BaseRect.Left >= OtherRect.Left && BaseRect.Right < OtherRect.Right) || (OtherRect.Left >= BaseRect.Left && OtherRect.Right < BaseRect.Right) || (OtherRect.Left >= BaseRect.Left && OtherRect.Left <= BaseRect.Right);
            if (WithinX)
                DeltaX = 0;
            WithinY = (OtherRect.Top >= BaseRect.Top && OtherRect.Top <= BaseRect.Bottom) || (OtherRect.Bottom >= BaseRect.Top && OtherRect.Bottom <= BaseRect.Bottom);
            if (WithinY)
                DeltaY = 0;
            //(BaseRect.Top >= OtherRect.Top && BaseRect.Bottom < OtherRect.Bottom) || (OtherRect.Top >= BaseRect.Top && OtherRect.Bottom < BaseRect.Bottom) || (OtherRect.Top >= BaseRect.Top && OtherRect.Top <= BaseRect.Bottom);

            //WithinX = OtherRect.Left >= BaseRect.Left && OtherRect.Right <= BaseRect.Right;
            //WithinY = OtherRect.Top >= BaseRect.Top && OtherRect.Bottom <= BaseRect.Bottom;
            Angle = 180.0 * Math.Atan2(DeltaY, DeltaX) / Math.PI;
            if (Angle < 0)
                Angle += 360;
        }

        /// <summary>
        /// Determine the closest points to zero
        /// </summary>
        /// <param name="InNumbers"></param>
        /// <returns></returns>
        private static double ClosestToZero(params double[] InNumbers)
        {
            for (int a = 1; a < InNumbers.Length; a++)
                if (Math.Abs(InNumbers[a]) < Math.Abs(InNumbers[0]))
                    InNumbers[0] = InNumbers[a];
            return InNumbers[0];
        }
    }
}
